package cmu.andersonlab.hardpropagate;

import java.awt.*;
import java.awt.geom.*;
import java.awt.font.FontRenderContext;
import javax.swing.*;

import java.awt.event.*;

import java.util.Timer;
import java.util.TimerTask;

import java.lang.Math;


public class trial extends JPanel implements MouseListener,MouseMotionListener {
    private tile[] tiles;
    private int arrow;
    private Color arrow_color;
    private int blue_tile;
    private String answer;
    public progress_listener progressor;
    public log lg = null;

    private String title_text;
    private Color title_color;
    private boolean its_all_over = false;
    private boolean feedback_visible;
    private boolean kp_visible;
    private boolean timed_out;
    private Rectangle done = new Rectangle(250, 50);
    private int kp_x = -1, kp_y = -1;
    private static int kp_width = 130, kp_height = 90; // 100, 70
    private static String[][] kp_keys = { { "*", "7", "8", "9", "+" },
                                          { "/", "4", "5", "6", "-" },
                                          { "0", "1", "2", "3", "bs" } };


    static Color green_arrow = new Color(0, 151, 0);
    static Color blue_arrow = new Color(52, 115, 255);

    static Stroke thin_stroke = new BasicStroke(1.0f);
    static Stroke medium_stroke = new BasicStroke(3.0f);
    static Stroke arrow_stroke = new BasicStroke(10.0f);
    static Font font = new Font("SansSerif", Font.PLAIN, 18);
    static Font title_font = new Font("Serif", Font.PLAIN, 48);
    static Font done_font = new Font("SansSerif", Font.PLAIN, 24);
    static int arrow_width = 12;
    static int arrow_height = 24;
    private Timer timer;

    public trial(String n1, String o1, String n2, String n3, String o2, String n4, int arrow, int blue_tile, String arrow_color, int pnum) {
        //System.out.println(String.format("%s, %s, %s, %s, %s, %s, arrow: %d, blue: %d", n1, o1, n2, n3, o2, n4, arrow, blue_tile));
        this.tiles = new tile[] { blue_tile == 0 ? tile.blue_tile(n1) : tile.gray_tile(n1),
                                  tile.gray_tile(o1),
                                  blue_tile == 2 ? tile.blue_tile(n2) : tile.gray_tile(n2),
                                  blue_tile == 3 ? tile.blue_tile(n3) : tile.gray_tile(n3),
                                  tile.gray_tile(o2),
                                  blue_tile == 5 ? tile.blue_tile(n4) : tile.gray_tile(n4) };
        this.arrow = arrow;
        if (arrow_color.equals("green")) {
            this.arrow_color = green_arrow;
        } else if (arrow_color.equals("blue")) {
            this.arrow_color = blue_arrow;
        } else {
            this.arrow_color = Color.gray;
        }
        this.blue_tile = blue_tile;
        this.feedback_visible = false;
        this.kp_visible = false;
        this.timed_out = false;

        this.title_text = String.format("Problem %d", pnum);
        this.title_color = Color.black;

        addMouseListener(this);
        //addMouseMotionListener(this);
    }

    public void start_feedback_timer () {
        timer = new Timer();
        timer.schedule(new TimerTask () {
                @Override
                public void run () {
                    end();
                }
            }, 2000);
    }

    public void start_problem_timer () {
        timer = new Timer();
        timer.schedule(new TimerTask () {
                @Override
                public void run () {
                    show_feedback(true);
                    start_feedback_timer();
                }
            }, 60000);
    }

    public void cancel_timer () {
        if (timer != null) timer.cancel();
        timer = null;
    }

    public void mouseEntered (MouseEvent e) { }
    public void mouseExited (MouseEvent e) { }
    public void mousePressed (MouseEvent e) { }

    public boolean inside_keypad (MouseEvent e) {
        return (e.getX() >= kp_x &&
                e.getX() <= kp_x+kp_width &&
                e.getY() >= kp_y &&
                e.getY() <= kp_y+kp_height);
    }

    public void click_keypad (MouseEvent e) {
        int w = kp_width/5;
        int h = kp_height/3;
        int col = (e.getX()-kp_x)/w;
        int row = (e.getY()-kp_y)/h;

        add_input(kp_keys[row][col]);
    }

    public void mouseClicked (MouseEvent e) { }

    public void mouseReleased (MouseEvent e) {
        if (!feedback_visible) {
            if (done.contains(e.getX(),e.getY())) {
                show_feedback(false);
                start_feedback_timer();
            } else if (e.getX() >= tiles[blue_tile].x &&
                       e.getX() <= tiles[blue_tile].right() &&
                       e.getY() >= tiles[blue_tile].y &&
                       e.getY() <= tiles[blue_tile].bottom()) {
                if (kp_visible) close_keypad();
                else open_keypad();
            } else if (kp_visible) {
                if (inside_keypad(e)) {
                    click_keypad(e);
                } else {
                    close_keypad();
                }
            }
        }
    }

    public void mouseMoved (MouseEvent e) { }
    public void mouseDragged (MouseEvent e) { }

    public void space_tiles(Graphics2D gr) {
        Dimension size = getSize();
        int tile_pad = 5;
        int box_pad = 5;

        for (int i=0; i<tiles.length; i++) {
            tiles[i].space(gr);
        }
        int row1 = size.height/2 - (50 + 40 + 50 + 90)/2;
        int row2 = row1 + 90;
        int box1_w = tiles[0].w + tiles[1].w + tiles[2].w + tile_pad*2 + box_pad*2;
        int box1_x = size.width/2 - box1_w/2;
        tiles[0].y = row1;
        tiles[0].x = box1_x + box_pad;
        tiles[1].y = row1;
        tiles[1].x = tiles[0].x + tiles[0].w + tile_pad;
        tiles[2].y = row1;
        tiles[2].x = tiles[1].x + tiles[1].w + tile_pad;

        tiles[4].x = tiles[1].x;
        tiles[3].y = row2;
        tiles[3].x = tiles[4].x - tile_pad - tiles[3].w;
        tiles[4].y = row2;
        tiles[5].y = row2;
        tiles[5].x = tiles[4].x + tiles[4].w + tile_pad;
    }

    public void draw_box(Graphics2D gr, int x, int y, int w, int h) {
        gr.setColor(new Color (230, 230, 230));
        gr.setStroke(thin_stroke);
        gr.fillRect(x, y, w, h);
        gr.setColor(Color.black);
        gr.drawRect(x, y, w, h);
    }

    public void draw_keypad (Graphics2D gr) {
        String arrow = new String(Character.toChars(2190));
        int h = kp_height / 3;
        int w = kp_width / 5;
        int ofsx, ofsy;

        if (kp_x == 0 && kp_y == 0) {
            System.out.println(String.format("it's at zero! %d", feedback_visible?1:0));
        }

        gr.setColor(Color.white);
        gr.fillRect(kp_x,kp_y,kp_width,kp_height);
        gr.setColor(Color.black);
        gr.setStroke(thin_stroke);
        // draw lines
        for (int c=0; c<=5; c++) {
            gr.draw(new Line2D.Double (kp_x+c*w, kp_y, kp_x+c*w, kp_y+kp_height-1));
        }
        for (int r=0; r<=3; r++) {
            gr.draw(new Line2D.Double (kp_x, kp_y+r*h, kp_x+kp_width, kp_y+r*h));
        }
        // draw labels
        gr.setFont(font);
        FontRenderContext frc = gr.getFontRenderContext();
        FontMetrics fm = gr.getFontMetrics();
        ofsy = 0;
        for (int r=0; r<3; r++) {
            ofsx = 0;
            for (int c=0; c<5; c++) {
                Rectangle2D bounds = font.getStringBounds(kp_keys[r][c], frc);
                gr.drawString(kp_keys[r][c], (int)(kp_x+ofsx+(w-bounds.getWidth())/2), (int)(kp_y+ofsy+h/2-bounds.getHeight()/2+fm.getAscent()));
                ofsx += w;
            }
            ofsy += h;
        }
    }

    public void draw_done(Graphics2D gr) {
        gr.setColor(new Color (200, 200, 200));
        gr.fillRoundRect(done.x, done.y, done.width, done.height, 20, 20);
        gr.setColor(Color.black);
        gr.setStroke(medium_stroke);
        gr.drawRoundRect(done.x, done.y, done.width, done.height, 20, 20);

        String text = "Done";
        FontRenderContext frc = gr.getFontRenderContext();
        Rectangle2D bounds = done_font.getStringBounds(text, frc);
        gr.setFont(done_font);
        FontMetrics fm = gr.getFontMetrics();
        gr.drawString(text, (int)(done.x + done.width/2 - bounds.getWidth()/2), (int)(done.y + done.height/2 - bounds.getHeight()/2 + fm.getAscent() ));
    }

    public void draw_arrow(Graphics2D gr, int x1, int y1, int x2, int y2) {
        double dx = x2 - x1;
        double dy = y2 - y1;
        double angle, angle2;

        if (dx == 0)
            angle = Math.PI/2;
        else if (dx < 0 && dy < 0)
            angle = -Math.atan(dy/dx);
        else if (dx < 0)
            angle = Math.atan(dy/dx)-Math.PI;
        else if (dy < 0)
            angle = Math.atan(dy/dx)-Math.PI/2;
        else
            angle = Math.atan(dy/dx);
        angle2 = angle + Math.PI/2;

        double end_x = x2 - arrow_height * Math.cos(angle);
        double end_y = y2 - arrow_height * Math.sin(angle);
        double end2_x = x2 - (arrow_height-1) * Math.cos(angle);
        double end2_y = y2 - (arrow_height-1) * Math.sin(angle);
        double ax = arrow_width*Math.cos(angle2);
        double ay = arrow_width*Math.sin(angle2);
        double left_side_x = end_x + ax;
        double left_side_y = end_y + ay;
        double right_side_x = end_x - ax;
        double right_side_y = end_y - ay;

        gr.setStroke(this.arrow_stroke);
        gr.setColor(arrow_color);

        gr.draw(new Line2D.Double(x1,y1, end2_x, end2_y));
        Polygon p = new Polygon();
        p.addPoint((int)left_side_x, (int)left_side_y);
        p.addPoint((int)right_side_x, (int)right_side_y);
        p.addPoint(x2, y2);
        gr.fillPolygon(p);
    }

    public void paintComponent (Graphics graphics) {
        super.paintComponent(graphics);
        Graphics2D gr = (Graphics2D) graphics;
        Dimension size = getSize();
        gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        gr.setColor(Color.white);
        gr.fillRect(0,0,size.width, size.height);
        // it seems we can only re-space when we paint
        space_tiles(gr);
        // it's weird if the keypad moves while clicking it
        if (!kp_visible || (kp_x == -1 && kp_y == -1)) {
            kp_x = tiles[blue_tile].x+(tiles[blue_tile].w-kp_width)/2;
            kp_y = tiles[blue_tile].y+tiles[blue_tile].h;
        }
        done.x = (size.width - done.width)/2;
        done.y = size.height - done.height - 30;
        // draw
        int pad = 5;
        int top_x = this.tiles[0].x - pad;
        int top_y = this.tiles[0].y - pad;
        int top_width = this.tiles[2].right() - this.tiles[0].x + pad*2;
        int top_height = this.tiles[2].bottom() - this.tiles[0].y + pad*2;
        int bot_x = this.tiles[3].x - pad;
        int bot_y = this.tiles[3].y - pad;
        int bot_width = this.tiles[5].right() - this.tiles[3].x + pad*2;
        int bot_height = this.tiles[5].bottom() - this.tiles[3].y + pad*2;
        // draw bottom box
        this.draw_box (gr, bot_x, bot_y, bot_width, bot_height);
        for (int i=3; i<6; i++) this.tiles[i].draw(gr);
        // draw arrow
        draw_arrow(gr,
                   top_x + top_width/2,
                   top_y + top_height,
                   this.tiles[arrow].x + this.tiles[arrow].w/2,
                   this.tiles[arrow].y);
        // draw top box
        this.draw_box (gr, top_x, top_y, top_width, top_height);
        for (int i=0; i<3; i++) this.tiles[i].draw(gr);
        //
        FontRenderContext frc = gr.getFontRenderContext();
        Rectangle2D bounds = title_font.getStringBounds(title_text, frc);
        gr.setFont(title_font);
        gr.setColor(title_color);
        gr.drawString(title_text, (int)((size.width-bounds.getWidth())/2), 50);

        if (kp_visible) {
            draw_keypad(gr);
        }

        if (!feedback_visible) {
            draw_done(gr);
        }
    }

    public String fmttile(tile t, int id) {
        if (id == blue_tile) {
            return String.format ("(i%d \"%s\" \"%s\")", id, t.value, t.answer);
        } else {
            return String.format ("(t%d \"%s\")", id, t.value);
        }
    }

    public String fmtbox (int id, int left, tile op, int right) {
        return String.format ("(b%d %s%d %s %s%d)",
                              id,
                              left == blue_tile ? "i":"t",
                              left,
                              op.value,
                              right == blue_tile ? "i":"t",
                              right);
    }

    public String fmtarrow (int from, int to) {
        return String.format ("(a b%d %s%d)",
                              from,
                              to == blue_tile ? "i":"t",
                              to);
    }

    public void start(boolean interactive) {
        String graph = String.format("(%s %s %s %s %s %s %s)",
                                     fmtbox(1,0,tiles[1],2),
                                     fmtbox(2,3,tiles[4],5),
                                     fmttile(tiles[0], 0),
                                     fmttile(tiles[2], 2),
                                     fmttile(tiles[3], 3),
                                     fmttile(tiles[5], 5),
                                     fmtarrow(1,arrow));
        lg.store("trial", "start", ":graph-objects", graph);
        if (interactive) start_problem_timer();
    }

    public boolean open_keypad() {
        if (feedback_visible || kp_visible) {
            return false;
        } else {
            this.kp_visible = true;
            lg.store("trial", "keypad-open", ":node", lg.fmtint(blue_tile));
            schedule_repaint();
            return true;
        }
    }

    public boolean close_keypad() {
        if (feedback_visible || !kp_visible) {
            return false;
        } else {
            lg.store("trial", "keypad-close", ":node", lg.fmtint(blue_tile));
            this.kp_visible = false;
            schedule_repaint();
            return true;
        }
    }

    public boolean show_feedback(boolean timeout) {
        if (feedback_visible) {
            return false;
        } else {
            cancel_timer();
            if (this.kp_visible) close_keypad();
            this.feedback_visible = true;
            this.timed_out = timeout;

            boolean correct = tiles[blue_tile].value.equals (tiles[blue_tile].answer);

            if (timeout) {
                this.title_text = "Time's Up!";
                title_color = Color.red;
            } else if (correct) {
                this.title_text = "Correct";
                title_color = Color.green;
            } else {
                this.title_text = "Incorrect";
                title_color = Color.red;
            }
            lg.store("trial", "feedback", ":correct", lg.fmtbool(correct), ":timed-out", lg.fmtbool(timeout));
            schedule_repaint();
            return true;
        }
    }

    public boolean add_input (String value) {
        if (this.kp_visible) {
            if (value.equals ("bs")) {
                String v = this.tiles[this.blue_tile].value;
                if (v.length() > 0) {
                    this.tiles[this.blue_tile].value = v.substring(0,v.length()-1);
                }
            } else {
                this.tiles[this.blue_tile].value += value;
            }
            lg.store("trial", "keypad-input", ":node", lg.fmtint(blue_tile), ":key", log.fmtstr(value));
            schedule_repaint();
            return true;
        } else {
            return false;
        }
    }

    public String dump () {
        String color;
        if (arrow_color == blue_arrow) {
            color = "blue";
        } else if (arrow_color == green_arrow) {
            color = "green";
        } else {
            color = "gray";
        }
        return String.format("%s,%s,%s,%s,%s,%s,%d,%d,%s,%s,%s",
                             this.tiles[0].value,
                             this.tiles[1].value,
                             this.tiles[2].value,
                             this.tiles[3].value,
                             this.tiles[4].value,
                             this.tiles[5].value,
                             this.blue_tile,
                             this.arrow,
                             color,
                             this.kp_visible ? "keypad":"no-keypad",
                             title_text);
    }

    public boolean end () {
        if (feedback_visible) {
            cancel_timer();
            lg.store("trial", "end");
            // sometimes a repaint is scheduled that doesn't get
            // called until after the problem is done. So avoid a
            // needless repaint.
            its_all_over = true;
            if (progressor != null) progressor.next_problem();
            return true;
        } else {
            return false;
        }
    }

    public void schedule_repaint () {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (!its_all_over) {
                        repaint();
                    }
                }
            });
    }

}
